<script setup lang='ts'>
import * as faceLandmarksDetection from '@tensorflow-models/face-landmarks-detection';
import * as tf from '@tensorflow/tfjs';
import { flatMap, forEach, map, max } from 'lodash';
import { Random } from 'mockjs';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { onMounted, ref } from 'vue';

// 容器大小
const width = ref(document.body.clientWidth);
const height = ref(document.body.clientHeight);

const videoDom = ref();
const modelDom = ref();

const scene = new THREE.Scene();

const detector_config_face = {
  runtime: 'mediapipe',
  solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh',
} as any;

onMounted(async () => {
  tf.getBackend();

  // 获取视频标签
  const video = videoDom.value;

  const model_face = faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh;

  // 设置视频源为摄像头
  navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
    video.srcObject = stream;
  });
  const model_container = modelDom.value;
  const model_width = width.value - 40;
  const model_height = height.value - 40;
  const camera = new THREE.PerspectiveCamera(75, model_width / model_height, 0.1, 1000);
  const renderer = new THREE.WebGLRenderer();
  const controls = new OrbitControls(camera, renderer.domElement);

  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(model_width, model_height);
  model_container.innerHtml = null;
  model_container.appendChild(renderer.domElement);
  controls.update();

  const detector_face = await faceLandmarksDetection.createDetector(model_face, detector_config_face);

  video.addEventListener('play', () => {
    const geometry = new THREE.BufferGeometry();
    const colorList = map(new Array(100).fill(0), () => Random.color());

    const animate = async () => {
      const faces: faceLandmarksDetection.Face[] = await detector_face.estimateFaces(video);

      forEach(faces, (face_item, n: number) => {
        const { keypoints = [] } = face_item ?? {};
        const position = flatMap(map(keypoints, i => [-i.x * 2, -i.y * 2, -i.z * 2]));
        const maxX = max(map(keypoints, i => -i.x)) ?? 0;
        const maxY = max(map(keypoints, i => -i.y)) ?? 0;
        const maxZ = max(map(keypoints, i => -i.z)) ?? 0;

        camera.position.set(maxX * 2.5, maxY * 3.2, maxZ * 15);
        camera.lookAt(maxX * 2.5, maxY * 3.2, maxZ * 15);

        //初始化存放顶点信息的序列化数组
        const vertices = new Float32Array(position);
        //3个为一组，表示一个顶点的xyz坐标
        const attribue = new THREE.BufferAttribute(vertices, 3);
        //设置顶点信息，第二个参数3表示三个数确定一个顶点
        geometry.setAttribute('position', attribue);
        //创建点材质
        const material = new THREE.PointsMaterial({
          color: colorList[n],
          size: 10
        });
        const mesh = new THREE.Points(geometry, material);

        scene.add(mesh);
      });

      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    };

    animate();
  });
});
</script>

<template>
  <video class="video" ref="videoDom" :width='width' :height='height' autoplay></video>
  <div class="model" ref="modelDom"></div>
</template>

<style lang='less' src='./style.less' scoped />